<?php
/*
 * 调用远程RESTful的客户端类 要求最低的PHP版本是5.2.0，并且还要支持以下库：cURL, Libxml 2.6.0 This class for
 * invoke remote RESTful Webservice The requirement of PHP version is 5.2.0 or
 * above, and support as below: cURL, Libxml 2.6.0 @Version: 0.0.1 alpha
 * @Created: 11:06:48 2010/11/23 @Author:    Edison tsai<dnsing@gmail.com>
 * @Blog:    http://www.timescode.com @Link:    http://www.dianboom.com
 */

class HttpRequestService {
    
    // URL Object
    private $ch;
    // ontains the last HTTP status code returned.
    public $http_code;
    // ontains the last API call.
    private $http_url;
    // et up the API root URL.
    public $api_url;
    // et timeout default.
    public $timeout = 10;
    // et connect timeout.
    public $connecttimeout = 30;
    // erify SSL Cert.
    public $ssl_verifypeer = false;
    // esponse format.
    public $format = ''; // Only support json & xml for extension
    public $decodeFormat = 'json'; // default is json
    public $_encode = 'utf-8';
    // ecode returned json data.
    // public $decode_json = true;
    // ontains the last HTTP headers returned.
    public $http_info = array ();
    public $http_header = array ();
    private $contentType;
    private $postFields;
    private static $paramsOnUrlMethod = array ('GET', 'DELETE' );
    private static $supportExtension = array ('json', 'xml' );
    // or tmpFile
    private $file = null;
    // et the useragnet.
    private static $userAgent = 'Timescode_RESTClient v0.0.1-alpha';
    
    public function __construct() {
        
        $this->ch = curl_init ();
        /*
         * cURL settings
         */
        curl_setopt ( $this->ch, CURLOPT_USERAGENT, self::$userAgent );
        curl_setopt ( $this->ch, CURLOPT_CONNECTTIMEOUT, $this->connecttimeout );
        curl_setopt ( $this->ch, CURLOPT_TIMEOUT, $this->timeout );
        curl_setopt ( $this->ch, CURLOPT_RETURNTRANSFER, TRUE );
        curl_setopt ( $this->ch, CURLOPT_AUTOREFERER, TRUE );
        #curl_setopt ( $this->ch, CURLOPT_FOLLOWLOCATION, TRUE );
        curl_setopt ( $this->ch, CURLOPT_HTTPHEADER, array ('Expect:' ) );
        curl_setopt ( $this->ch, CURLOPT_SSL_VERIFYPEER, $this->ssl_verifypeer );
        curl_setopt ( $this->ch, CURLOPT_HEADERFUNCTION, array ($this, 'getHeader' ) );
        curl_setopt ( $this->ch, CURLOPT_HEADER, FALSE );
    
    }
    
    /**
     * Execute calls
     *
     * @param $url String           
     * @param $method String           
     * @param $postFields String           
     * @param $username String           
     * @param $password String           
     * @param $contentType String           
     * @return RESTClient
     */
    public function call($url, $method, $postFields = null, $username = null, $password = null, $contentType = null) {
        
        if (strrpos ( $url, 'https://' ) !== 0 && strrpos ( $url, 'http://' ) !== 0 && ! empty ( $this->format )) {
            $url = "{$this->api_url}{$url}.{$this->format}";
        }
        
        $this->http_url = $url;
        $this->contentType = $contentType;
        $this->postFields = $postFields;
        
        $url = in_array ( $method, self::$paramsOnUrlMethod ) ? $this->to_url () : $this->get_http_url ();
        
        is_object ( $this->ch ) or $this->__construct ();
        
        switch ($method) {
            case 'POST' :
                curl_setopt ( $this->ch, CURLOPT_POST, TRUE );
                if ($this->postFields != null) {
                    curl_setopt ( $this->ch, CURLOPT_POSTFIELDS, $this->postFields );
                }
                break;
            case 'DELETE' :
                curl_setopt ( $this->ch, CURLOPT_CUSTOMREQUEST, 'DELETE' );
                break;
            case 'PUT' :
                curl_setopt ( $this->ch, CURLOPT_PUT, TRUE );
                if ($this->postFields != null) {
                    $this->file = tmpFile ();
                    fwrite ( $this->file, $this->postFields );
                    fseek ( $this->file, 0 );
                    curl_setopt ( $this->ch, CURLOPT_INFILE, $this->file );
                    curl_setopt ( $this->ch, CURLOPT_INFILESIZE, strlen ( $this->postFields ) );
                }
                break;
        }
        
        $this->setAuthorizeInfo ( $username, $password );
        $this->contentType != null && curl_setopt ( $this->ch, CURLOPT_HTTPHEADER, array ('Content-type:' . $this->contentType ) );
        
        curl_setopt ( $this->ch, CURLOPT_URL, $url );
        
        $response = curl_exec ( $this->ch );
        $this->http_code = curl_getinfo ( $this->ch, CURLINFO_HTTP_CODE );
        $this->http_info = array_merge ( $this->http_info, curl_getinfo ( $this->ch ) );
        
        $this->close ();
        
        return $response;
    }
    public function call_fopen($url, $method, $postFields = null, $username = null, $password = null, $contentType = null) {
        
        if (strrpos ( $url, 'https://' ) !== 0 && strrpos ( $url, 'http://' ) !== 0 && ! empty ( $this->format )) {
            $url = "{$this->api_url}{$url}.{$this->format}";
        }
        
        $this->http_url = $url;
        $this->contentType = $contentType;
        $this->postFields = $postFields;
        
        $url = in_array ( $method, self::$paramsOnUrlMethod ) ? $this->to_url () : $this->get_http_url ();
        $params = array ('http' => array ('method' => 'POST', 'header' => 'Content-type: application/x-www-form-urlencoded' . "\r\n", 'content' => $this->create_post_body ( $this->postFields ) ) );
        $ctx = stream_context_create ( $params );
        $fp = fopen ( $url, 'rb', false, $ctx );
        
        if (! $fp) {
            die ( "can not open server!" );
        }
        
        $response = @stream_get_contents ( $fp );
        if ($response === false) {
            die ( "can not get message form server!" );
            // throw new Exception("Problem reading data from {$url},
            // {$php_errormsg}");
        }
        return $response;
    }
    
    public function setEncode($encode) {
        ! empty ( $encode ) and $this->_encode = $encode;
        return $this;
    }
    public static function convertEncoding($source, $in, $out) {
        $in = strtoupper ( $in );
        $out = strtoupper ( $out );
        if ($in == "UTF8") {
            $in = "UTF-8";
        }
        if ($out == "UTF8") {
            $out = "UTF-8";
        }
        if ($in == $out) {
            return $source;
        }
        
        if (function_exists ( 'mb_convert_encoding' )) {
            return mb_convert_encoding ( $source, $out, $in );
        } elseif (function_exists ( 'iconv' )) {
            return iconv ( $in, $out . "//IGNORE", $source );
        }
        return $source;
    }
    /**
     * POST wrapper，不基于curl函数，环境可以不支持curl函数
     *
     * @param
     *            method String
     * @param
     *            parameters Array
     * @return mixed
     */
    public function do_post_request($url, $postdata, $files) {
        $data = "";
        $boundary = "---------------------" . substr ( md5 ( rand ( 0, 32000 ) ), 0, 10 );
        
        // Collect Postdata
        foreach ( $postdata as $key => $val ) {
            $data .= "--$boundary\r\n";
            $data .= "Content-Disposition: form-data; name=\"" . $key . "\"\r\n\r\n" . $val . "\r\n";
        }
        $data .= "--$boundary\r\n";
        
        // Collect Filedata
        foreach ( $files as $key => $file ) {
            $fileContents = file_get_contents ( $file ['tmp_name'] );
            $data .= "Content-Disposition: form-data; name=\"{$key}\"; filename=\"{$file['name']}\"\r\n";
            $data .= "Content-Type: " . $file ['type'] . "\r\n";
            $data .= "Content-Transfer-Encoding: binary\r\n\r\n";
            $data .= $fileContents . "\r\n";
            $data .= "--$boundary--\r\n";
        }
        
        $params = array ('http' => array ('method' => 'POST', 'header' => 'Content-Type: multipart/form-data; boundary=' . $boundary, 'content' => $data ) );
        
        $ctx = stream_context_create ( $params );
        $fp = fopen ( $url, 'rb', false, $ctx );
        
        if (! $fp) {
            die ( "can not open server!" );
        }
        
        $response = @stream_get_contents ( $fp );
        if ($response === false) {
            die ( "can not get message form server!" );
            // throw new Exception("Problem reading data from {$url},
            // {$php_errormsg}");
        }
        return $response;
    }
    /**
     * POST wrapper for insert data
     *
     * @param $url String           
     * @param $params mixed           
     * @param $username String           
     * @param $password String           
     * @param $contentType String           
     * @return RESTClient
     */
    public function _POST($url, $params = null, $username = null, $password = null, $contentType = null) {
        $response = $this->call ( $url, 'POST', $params, $username, $password, $contentType );
        // $this->convertEncoding($response,'utf-8',$this->_encode)
        return $this->json_foreach ( $this->parseResponse ( $response ) );
    }
    
    public function _POST_FOPEN($url, $params = null, $username = null, $password = null, $contentType = null) {
        $response = $this->call_fopen ( $url, 'POST', $params, $username, $password, $contentType );
        return $this->json_foreach ( $this->parseResponse ( $response ) );
    }
    public function _photoUpload($url, $postdata, $files) {
        $response = $this->do_post_request ( $url, $postdata, $files );
        return $this->json_foreach ( $this->parseResponse ( $response ) );
    }
    // 将stdclass object转换成数组，并转换编码
    public function json_foreach($jsonArr) {
        if (is_object ( $jsonArr )) {
            $jsonArr = get_object_vars ( $jsonArr );
        }
        foreach ( $jsonArr as $k => $v ) {
            if (is_array ( $v )) {
                $jsonArr [$k] = $this->json_foreach ( $v );
            } elseif (is_object ( $v )) {
                $v = get_object_vars ( $v );
                $jsonArr [$k] = $this->json_foreach ( $v );
            } else {
                $v = $this->convertEncoding ( $v, "utf-8", $this->_encode );
                $jsonArr [$k] = $v;
            }
        
        }
        return $jsonArr;
    }
    /**
     * PUT wrapper for update data
     *
     * @param $url String           
     * @param $params mixed           
     * @param $username String           
     * @param $password String           
     * @param $contentType String           
     * @return RESTClient
     */
    public function _PUT($url, $params = null, $username = null, $password = null, $contentType = null) {
        $response = $this->call ( $url, 'PUT', $params, $username, $password, $contentType );
        return $this->parseResponse ( $this->convertEncoding ( $response, 'utf-8', $this->_encode ) );
    }
    public function create_post_body($post_params) {
        $params = array ();
        foreach ( $post_params as $key => &$val ) {
            if (is_array ( $val )) {
                $val = implode ( ',', $val );
            }
            $params [] = $key . '=' . urlencode ( $val );
        }
        return implode ( '&', $params );
    }
    /**
     * GET wrapper for get data
     *
     * @param $url String           
     * @param $params mixed           
     * @param $username String           
     * @param $password String           
     * @return RESTClient
     *
     */
    public function _GET($url, $params = null, $username = null, $password = null) {
        $response = $this->call ( $url, 'GET', $params, $username, $password );
        return $this->parseResponse ( $response );
    }
    
    /**
     * DELETE wrapper for delete data
     *
     * @param $url String           
     * @param $params mixed           
     * @param $username String           
     * @param $password String           
     * @return RESTClient
     */
    public function _DELETE($url, $params = null, $username = null, $password = null) {
        // odified by Edison tsai on 09:50 2010/11/26 for missing part
        $response = $this->call ( $url, 'DELETE', $params, $username, $password );
        return $this->parseResponse ( $response );
    }
    
    /*
     * Parse response, including json, xml, plain text @param $resp String
     * @param $ext    String, including json/xml @return String
     */
    public function parseResponse($resp, $ext = '') {
        
        $ext = ! in_array ( $ext, self::$supportExtension ) ? $this->decodeFormat : $ext;
        
        switch ($ext) {
            case 'json' :
                $resp = json_decode ( $resp );
                break;
            case 'xml' :
                $resp = self::xml_decode ( $resp );
                break;
        }
        return $resp;
    }
    
    /*
     * XML decode @param $data String @param $toArray boolean, true for make it
     * be array @return String
     */
    public static function xml_decode($data, $toArray = false) {
        /*
         * TODO: What to do with 'toArray'? Just write it as you need.
         */
        $data = simplexml_load_string ( $data );
        return $data;
    }
    
    public static function objectToArray($obj) {
    
    }
    
    /**
     * parses the url and rebuilds it to be
     * scheme://host/path
     */
    public function get_http_url() {
        $parts = parse_url ( $this->http_url );
        
        $port = @$parts ['port'];
        $scheme = $parts ['scheme'];
        $host = $parts ['host'];
        $path = @$parts ['path'];
        
        $port or $port = ($scheme == 'https') ? '443' : '80';
        
        if (($scheme == 'https' && $port != '443') || ($scheme == 'http' && $port != '80')) {
            $host = "$host:$port";
        }
        return "$scheme://$host$path";
    }
    
    /**
     * builds a url usable for a GET request
     */
    public function to_url() {
        $post_data = $this->to_postdata ();
        $out = $this->get_http_url ();
        if ($post_data) {
            $out .= '?' . $post_data;
        }
        return $out;
    }
    
    /**
     * builds the data one would send in a POST request
     */
    public function to_postdata() {
        return http_build_query ( $this->postFields );
    }
    
    /**
     * Settings that won't follow redirects
     *
     * @return RESTClient
     *
     */
    public function setNotFollow() {
        curl_setopt ( $this->ch, CURLOPT_AUTOREFERER, FALSE );
        curl_setopt ( $this->ch, CURLOPT_FOLLOWLOCATION, FALSE );
        return $this;
    }
    
    /**
     * Closes the connection and release resources
     *
     * @return void
     *
     */
    public function close() {
        curl_close ( $this->ch );
        if ($this->file != null) {
            fclose ( $this->file );
        }
    }
    
    /**
     * Sets the URL to be Called
     *
     * @param $url String           
     * @return void
     *
     */
    public function setURL($url) {
        $this->url = $url;
    }
    
    /**
     * Sets the format type to be extension
     *
     * @param $format String           
     * @return boolean
     */
    public function setFormat($format = null) {
        if ($format == null)
            return false;
        $this->format = $format;
        return true;
    }
    
    /**
     * Sets the format type to be decoded
     *
     * @param $format String           
     * @return boolean
     */
    public function setDecodeFormat($format = null) {
        if ($format == null)
            return false;
        $this->decodeFormat = $format;
        return true;
    }
    
    /**
     * Set the Content-Type of the request to be send
     * Format like "application/json" or "application/xml" or "text/plain" or
     * other
     *
     * @param $contentType string           
     * @return void
     */
    public function setContentType($contentType) {
        $this->contentType = $contentType;
    }
    
    /**
     * Set the authorize info for Basic Authentication
     *
     * @param $username String           
     * @param $password String           
     * @return void
     */
    public function setAuthorizeInfo($username, $password) {
        if ($username != null) { // he password might be blank
            curl_setopt ( $this->ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC );
            curl_setopt ( $this->ch, CURLOPT_USERPWD, "{$username}:{$password}" );
        }
    }
    
    /**
     * Set the Request HTTP Method
     *
     * @param $method String           
     * @return void
     */
    public function setMethod($method) {
        $this->method = $method;
    }
    
    /**
     * Set Parameters to be send on the request
     * It can be both a key/value par array (as in array("key"=>"value"))
     * or a string containing the body of the request, like a XML, JSON or other
     * Proper content-type should be set for the body if not a array
     *
     * @param $params mixed           
     * @return void
     */
    public function setParameters($params) {
        $this->postFields = $params;
    }
    
    /**
     * Get the header info to store.
     */
    public function getHeader($ch, $header) {
        $i = strpos ( $header, ':' );
        if (! empty ( $i )) {
            $key = str_replace ( '-', '_', strtolower ( substr ( $header, 0, $i ) ) );
            $value = trim ( substr ( $header, $i + 2 ) );
            $this->http_header [$key] = $value;
        }
        return strlen ( $header );
    }

}


class RenrenRestApiService extends HttpRequestService {
    
    private $_config;
    private $_postFields = '';
    private $_params = array ();
    private $_currentMethod;
    private static $_sigKey = 'sig';
    private $_sig = '';
    private $_call_id = '';
    
    private $_keyMapping = array ('api_key' => '', 'method' => '', 'v' => '', 'format' => '' );
    
    public function __construct() {
        parent::__construct ();
        $this->_config->APIURL = 'http://api.renren.com/restserver.do';
        $this->_config->APIKey = OPENAPI_RENREN_APP_KEY;
        $this->_config->SecretKey = OPENAPI_RENREN_APP_TOKEN;
        $this->_config->APIVersion = '1.0';
        $this->_config->decodeFormat = 'json';
        if (empty ( $this->_config->APIURL ) || empty ( $this->_config->APIKey ) || empty ( $this->_config->SecretKey )) {
            throw new exception ( 'Invalid API URL or API key or Secret key, please check config.inc.php' );
        }
    
    }
    
    /**
     * GET wrapper
     *
     * @param
     *            method String
     * @param
     *            parameters Array
     * @return mixed
     */
    public function GET() {
        
        $args = func_get_args ();
        $this->_currentMethod = trim ( $args [0] ); // ethod
        $this->paramsMerge ( $args [1] )->getCallId ()->setConfigToMapping ()->generateSignature ();
        
        // nvoke
        unset ( $args );
        
        return $this->_GET ( $this->_config->APIURL, $this->_params );
    
    }
    
    /**
     * POST wrapper，基于curl函数，需要支持curl函数才行
     *
     * @param
     *            method String
     * @param
     *            parameters Array
     * @return mixed
     */
    public function rr_post_curl() {
        
        $args = func_get_args ();
        $this->_currentMethod = trim ( $args [0] ); // ethod
        $this->paramsMerge ( $args [1] )->getCallId ()->setConfigToMapping ()->generateSignature ();
        
        // nvoke
        unset ( $args );
        
        return $this->_POST ( $this->_config->APIURL, $this->_params );
    
    }
    /**
     * Generate signature for sig parameter
     *
     * @param
     *            method String
     * @param
     *            parameters Array
     * @return RenRenClient
     */
    private function generateSignature() {
        $arr = array_merge ( $this->_params, $this->_keyMapping );
        ksort ( $arr );
        reset ( $arr );
        $str = '';
        foreach ( $arr as $k => $v ) {
            $v = $this->convertEncoding ( $v, $this->_encode, "utf-8" );
            $arr [$k] = $v; // 转码，你懂得
            $str .= $k . '=' . $v;
        }
        
        $this->_params = $arr;
        $str = md5 ( $str . $this->_config->SecretKey );
        $this->_params [self::$_sigKey] = $str;
        $this->_sig = $str;
        
        unset ( $str, $arr );
        
        return $this;
    }
    
    /**
     * Parameters merge
     *
     * @param $params Array
     *            @modified by Edison tsai on 15:56 2011/01/13 for fix
     *            non-object bug
     * @return RenRenClient
     */
    private function paramsMerge($params) {
        $this->_params = $params;
        return $this;
    }
    
    /**
     * Setting mapping value
     * @modified by Edison tsai on 15:04 2011/01/13 for add call id &
     * session_key
     *
     * @return RenRenClient
     */
    private function setConfigToMapping() {
        $this->_keyMapping ['api_key'] = $this->_config->APIKey;
        $this->_keyMapping ['method'] = $this->_currentMethod;
        $this->_keyMapping ['v'] = $this->_config->APIVersion;
        $this->_keyMapping ['format'] = $this->_config->decodeFormat;
        return $this;
    }
    
    private function setAPIURL($url) {
        $this->_config->APIURL = $url;
    }
    
    /**
     * Generate call id
     *
     * @author Edison tsai
     *         @created 14:48 2011/01/13
     * @return RenRenClient
     */
    public function getCallId() {
        $this->_call_id = str_pad ( mt_rand ( 1, 9999999999 ), 10, 0, STR_PAD_RIGHT );
        return $this;
    }
    
    public function rr_post_fopen() {
        
        $args = func_get_args ();
        $this->_currentMethod = trim ( $args [0] ); // ethod
        $this->paramsMerge ( $args [1] )->getCallId ()->setConfigToMapping ()->generateSignature ();
        
        // nvoke
        unset ( $args );
        
        return $this->_POST_FOPEN ( $this->_config->APIURL, $this->_params );
    
    }
    public function rr_photo_post_fopen() {
        
        $args = func_get_args ();
        $this->_currentMethod = trim ( $args [0] ); // ethod
        $this->paramsMerge ( $args [1] )->getCallId ()->setConfigToMapping ()->generateSignature ();
        
        // nvoke
        $photo_files = $args [2];
        
        unset ( $args );
        return $this->_photoUpload ( $this->_config->APIURL, $this->_params, $photo_files );
    
    }

}
?>
